package com.wanbatv.wangwangba.util;

import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.JsonReader;
import android.widget.ImageView;

import com.squareup.okhttp.Callback;
import com.squareup.okhttp.FormEncodingBuilder;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.ResponseBody;
import com.squareup.picasso.Picasso;
import com.wanbatv.wangwangba.utils.Product;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

/**
 * 资源加载模块
 */
public class ResourceLoader {

    private static final int MSG_EVENT_CHECK = 1;

    /**
     * 检查资源是否有更新的回调接口
     */
    public interface CheckResultCallback {

        /**
         * 检查结束后，通过该接口通知是否需要更新资源
         * @param needUpdate true 表示需要更新资源
         */
        void onCheckResult(boolean needUpdate);
    }

    /**
     * 资源更新监听器
     */
    public interface OnUpdateResourceListener {

        /**
         * 开始更新的回调接口
         */
        void onUpdateStart();

        /**
         * 更新某个资源完成后的回调接口
         * @param url 资源地址
         */
        void onUpdatingCompletion(String url);

        /**
         * 更新结束时的回调接口
         */
        void onUpdateFinish();
    }

    /**
     * 准备监听器
     */
    public interface OnPrepareListener {

        /**
         * 准备好后的回调接口
         */
        void onPrepared();
    }

    private static ResourceLoader sInstance = null;

    private Context mContext = null;
    private CheckResultCallback mCheckResultCallback = null;
    private OnUpdateResourceListener mOnLoadResourceListener = null;
    private OnPrepareListener mOnPrepareListener = null;
    private Options mOptions = null;
    private CallbackHandler mHandler = null;
    private ResourceInfoFactory mResFactory = null;
    private ResourceInfo mResourceInfo = null;
    private final String RESOURCE_DIR;

    /**
     * 以单例模式获取资源加载器的实例
     * @param context 应用上下文对象
     * @return 资源加载器的实例
     */
    public static ResourceLoader getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new ResourceLoader(context);
        }
        return sInstance;
    }

    private ResourceLoader(Context context) {
        mContext = context;
        mHandler = new CallbackHandler(this);
        mResFactory = new ResourceInfoFactory();
        mResourceInfo = new ResourceInfo();
        SharedPreferences sp = mContext.getSharedPreferences("config", Context.MODE_PRIVATE);
        mResourceInfo.version = sp.getString("res.version", null);
        RESOURCE_DIR = context.getFileStreamPath("resource").getAbsolutePath();
    }

    public void setCheckResultCallback(CheckResultCallback callback) {
        mCheckResultCallback = callback;
    }

    private void performCheckResultCallback(boolean needUpdate) {
        if (mCheckResultCallback != null) {
            mCheckResultCallback.onCheckResult(needUpdate);
        }
    }

    public void setOnUpdateResourceListener(OnUpdateResourceListener listener) {
        mOnLoadResourceListener = listener;
    }

    public void setOnPrepareListener(OnPrepareListener listener) {
        mOnPrepareListener = listener;
    }

    public void init(Options options) {
        mOptions = options;
    }

    private void checkInit() {
        if (mOptions == null) {
            throw new RuntimeException("需要先调用init()方法初始化ResourceLoader参数");
        }
    }

    public void prepare() {
        checkInit();
        PrepareArgument argument = new PrepareArgument(RESOURCE_DIR, mResourceInfo, mOnPrepareListener);
        new PrepareTask(argument).execute();
    }

    public void checkUpdate() {
        checkInit();

        final OkHttpClient httpClient = new OkHttpClient();
        FormEncodingBuilder postBodyBuilder = new FormEncodingBuilder()
                .add("flavor", Product.flavor)
                .add("stb", Util.getDeviceId(mContext));
        String version = mResourceInfo.version;
        postBodyBuilder.add("version", version != null ? version : "");
        Request.Builder builder = new Request.Builder()
                .url("http://" + mOptions.host + ":" + mOptions.port + "/api/wangwangba/check")
                .post(postBodyBuilder.build());
        httpClient.newCall(builder.build()).enqueue(new Callback() {
            @Override
            public void onFailure(Request request, IOException e) {
                Message msg = mHandler.obtainMessage(MSG_EVENT_CHECK);
                msg.arg1 = 0;
                mHandler.sendMessage(msg);
            }

            @Override
            public void onResponse(Response response) throws IOException {
                if (response.isSuccessful()) {
                    final ResponseBody body = response.body();
                    mResourceInfo = mResFactory.parse(mResourceInfo, body.charStream());
                    Message msg = mHandler.obtainMessage(MSG_EVENT_CHECK);
                    msg.arg1 = mResourceInfo.needUpdate ? 1 : 0;
                    mHandler.sendMessage(msg);
                } else {
                    Message msg = mHandler.obtainMessage(MSG_EVENT_CHECK);
                    msg.arg1 = 0;
                    mHandler.sendMessage(msg);
                }
            }
        });
    }

    public void update() {
        checkInit();
        if (mResourceInfo != null) {
            UpdateArgument argument = new UpdateArgument(RESOURCE_DIR, mHandler, mOnLoadResourceListener);
            new UpdateTask(argument).execute();
        } else {
            if (mOnLoadResourceListener != null) {
                mOnLoadResourceListener.onUpdateFinish();
            }
        }
    }

    private void loadInner(List<ResourceItem> resItems, int defResId, ImageView view) {
        checkInit();
        boolean loaded = false;
        if (resItems != null) {
            final int length = resItems.size();
            long currentTime = System.currentTimeMillis();
            ResourceItem item;
            for (int i = 0; i < length; i++) {
                item = resItems.get(i);
                if (item.beginTime >= currentTime && item.endTime < currentTime) {
                    String file = "file://" + RESOURCE_DIR + "/" + item.name;
                    Picasso.with(mContext).load(Uri.parse(file)).into(view);
                    loaded = true;
                }
            }
        }
        if (!loaded) {
            Picasso.with(mContext).load(defResId).into(view);
        }
    }

    public void loadAppBackground(ImageView view) {
        loadInner(mResourceInfo != null ? mResourceInfo.appBackgrounds : null,
                mOptions.defAppBackgroundResId, view);
    }

    public void loadWelcomeBackground(ImageView view) {
        loadInner(mResourceInfo != null ? mResourceInfo.welcomeBackgrounds : null,
                mOptions.defWelcomeBackgroundResId, view);
    }

    public void loadOrderWindowBackground(ImageView view) {
        loadInner(mResourceInfo != null ? mResourceInfo.orderWindowBackgrounds : null,
                mOptions.defOrderWindowBackgroundResId, view);
    }

    public static class Options {

        public final int defAppBackgroundResId;
        public final int defWelcomeBackgroundResId;
        public final int defOrderWindowBackgroundResId;
        public final String host;
        public final int port;

        Options(OptionsBuilder builder) {
            this.defAppBackgroundResId = builder.defAppBackgroundResId;
            this.defWelcomeBackgroundResId = builder.defWelcomeBackgroundResId;
            this.defOrderWindowBackgroundResId = builder.defOrderWindowBackgroundResId;
            this.host = builder.host;
            this.port = builder.port;
        }

    }

    public static class OptionsBuilder {

        int defAppBackgroundResId;
        int defWelcomeBackgroundResId;
        int defOrderWindowBackgroundResId;
        String host;
        int port;

        public OptionsBuilder() {
            this.host = "121.201.14.248";
            this.port = 8062;
        }

        public OptionsBuilder defAppBackgroundResId(int resId) {
            this.defAppBackgroundResId = resId;
            return this;
        }

        public OptionsBuilder defWelcomeBackgroundResId(int resId) {
            this.defWelcomeBackgroundResId = resId;
            return this;
        }

        public OptionsBuilder defOrderWindowBackgroundResId(int resId) {
            this.defOrderWindowBackgroundResId = resId;
            return this;
        }

        public OptionsBuilder host(String host) {
            this.host = host;
            return this;
        }

        public OptionsBuilder port(int port) {
            this.port = port;
            return this;
        }

        Options build() {
            return new Options(this);
        }
    }

    private static class CallbackHandler extends Handler {

        private WeakReference<ResourceLoader> mLoaderRef = null;

        CallbackHandler(ResourceLoader loader) {
            super(Looper.getMainLooper());
            mLoaderRef = new WeakReference<>(loader);
        }

        @Override
        public void handleMessage(Message msg) {
            final ResourceLoader loader = mLoaderRef.get();
            if (loader == null) {
                return;
            }
            switch (msg.what) {
                case MSG_EVENT_CHECK:
                    loader.performCheckResultCallback(msg.arg1 == 1);
                    break;
            }
        }
    }

    private static class ResourceItem {
        final String name;
        final long beginTime;
        final long endTime;
        final String url;

        ResourceItem(String name, long beginTime, long endTime, String url) {
            this.name = name;
            this.beginTime = beginTime;
            this.endTime = endTime;
            this.url = url;
        }
    }

    private static class ResourceInfo {
        boolean needUpdate = false;
        String version = null;
        List<ResourceItem> appBackgrounds = null;
        List<ResourceItem> welcomeBackgrounds = null;
        List<ResourceItem> orderWindowBackgrounds = null;

        ResourceInfo() {
            appBackgrounds = new ArrayList<>();
            welcomeBackgrounds = new ArrayList<>();
            orderWindowBackgrounds = new ArrayList<>();
        }

        void reset() {
            appBackgrounds.clear();
            welcomeBackgrounds.clear();
            orderWindowBackgrounds.clear();
        }
    }

    private static class ResourceInfoFactory {

        private void parseItems(JsonReader reader, List<ResourceItem> itemContainer) throws IOException {
            reader.beginArray();
            String arrName;
            while (reader.hasNext()) {
                reader.beginObject();
                String itemName = null;
                String itemUrl = null;
                long itemBegin = 0l;
                long itemEnd = 0l;
                while (reader.hasNext()) {
                    arrName = reader.nextName();
                    if (arrName.equals("url")) {
                        itemUrl = reader.nextString();
                    } else if (arrName.equals("begin")) {
                        itemBegin = reader.nextLong();
                    } else if (arrName.equals("end")) {
                        itemEnd = reader.nextLong();
                    } else if (arrName.equals("name")) {
                        itemName = reader.nextString();
                    } else {
                        reader.skipValue();
                    }
                }
                itemContainer.add(new ResourceItem(itemName, itemBegin, itemEnd, itemUrl));
                reader.endObject();
            }
            reader.endArray();
        }

        ResourceInfo parse(ResourceInfo info, Reader in) throws IOException {
            if (info == null) {
                info = new ResourceInfo();
            }
            final JsonReader reader = new JsonReader(in);
            reader.beginObject();
            String name;
            while (reader.hasNext()) {
                name = reader.nextName();
                if (name.equals("data")) {
                    reader.beginObject();
                    String dataName;
                    while (reader.hasNext()) {
                        dataName = reader.nextName();
                        if (dataName.equals("needUpdate")) {
                            info.needUpdate = reader.nextBoolean();
                        } else if (dataName.equals("version")) {
                            info.version = reader.nextString();
                        } else if (dataName.equals("appBackgrounds")) {
                            parseItems(reader, info.appBackgrounds);
                        } else if (dataName.equals("welcomeBackgrounds")) {
                            parseItems(reader, info.welcomeBackgrounds);
                        } else if (dataName.equals("orderWindowBackgrounds")) {
                            parseItems(reader, info.orderWindowBackgrounds);
                        } else {
                            reader.skipValue();
                        }
                    }
                    reader.endObject();
                } else {
                    reader.skipValue();
                }
            }
            reader.endObject();

            return info;
        }
    }

    private static class PrepareArgument {

        private final String mResRoot;
        private final ResourceInfo mResourceInfo;
        private final WeakReference<OnPrepareListener> mListenerRef;

        PrepareArgument(String root, ResourceInfo resourceInfo, OnPrepareListener listener) {
            mResRoot = root;
            mResourceInfo = resourceInfo;
            mListenerRef = new WeakReference<>(listener);
        }
    }

    private static class PrepareTask extends AsyncTask<Void, Void, Void> {

        private PrepareArgument mArguments = null;

        PrepareTask(PrepareArgument argument) {
            mArguments = argument;
        }

        @Override
        protected Void doInBackground(Void... params) {
            final String RES_DIR = mArguments.mResRoot;
            File resDir = new File(RES_DIR);
            if (resDir.isDirectory()) {
                final ResourceInfo resInfo = mArguments.mResourceInfo;
                resInfo.reset();
                File[] list = resDir.listFiles();
                for (File f : list) {
                    if (f.getName().endsWith(".cfg")) {
                        try {
                            BufferedReader reader = new BufferedReader(new InputStreamReader(
                                    new FileInputStream(f), "utf-8"));
                            String line;
                            StringBuilder sb = new StringBuilder();
                            while ((line = reader.readLine()) != null) {
                                sb.append(line);
                            }
                            JSONObject configJson = (JSONObject) new JSONTokener(sb.toString()).nextValue();
                            boolean append = false;
                            if (appendResItem(configJson, resInfo.appBackgrounds, "appBackgrounds")
                                    || appendResItem(configJson, resInfo.welcomeBackgrounds, "welcomeBackgrounds")
                                    || appendResItem(configJson, resInfo.orderWindowBackgrounds, "orderWindowBackgrounds")) {
                                append = true;
                            }
                            if (!append) {
                                f.delete();
                            }
                            reader.close();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }

            }
            return null;
        }

        private boolean appendResItem(JSONObject from, List<ResourceItem> to, String key) {
            boolean append = false;
            if (from.has(key)) {
                try {
                    JSONArray appBackgrounds = from.getJSONArray(key);
                    final int length = appBackgrounds.length();
                    String name = null;
                    long beginTime = 0l;
                    long endTime = 0l;
                    String url = null;
                    for (int i = 0; i < length; i++) {
                        final JSONObject item = appBackgrounds.getJSONObject(i);
                        if (item.has("name")) {
                            name = item.getString("name");
                        }
                        if (item.has("begin")) {
                            beginTime = item.getLong("begin");
                        }
                        if (item.has("end")) {
                            endTime = item.getLong("end");
                        }
                        if (item.has("url")) {
                            url = item.getString("url");
                        }
                        final long now = System.currentTimeMillis();
                        if (beginTime >= now && endTime < now) {
                            to.add(new ResourceItem(name, beginTime, endTime, url));
                            if (!append) {
                                append = true;
                            }
                        }
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
            return append;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            if (mArguments != null) {
                OnPrepareListener listener = mArguments.mListenerRef.get();
                if (listener != null) {
                    listener.onPrepared();
                }
            }
        }
    }

    private static class UpdateArgument {
        private ResourceItem[] mResourceItems = null;
        final private String mRoot;
        final private WeakReference<OnUpdateResourceListener> mListenerRef;
        final Handler mHandler;

        UpdateArgument(String root, Handler handler, OnUpdateResourceListener listener) {
            mRoot = root;
            mHandler = handler;
            mListenerRef = new WeakReference<>(listener);
        }
    }

    private static class UpdateTask extends AsyncTask<Void, Void, Void> {

        private UpdateArgument mArgument = null;
        private Downloader mDownloader = null;

        UpdateTask(UpdateArgument argument) {
            mArgument = argument;
            mDownloader = new Downloader(mArgument.mRoot);
            mDownloader.setCallback(new DownloaderAdapter() {
                @Override
                void onCompletion(final String url) {
                    mArgument.mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            OnUpdateResourceListener listener = mArgument.mListenerRef.get();
                            if (listener != null) {
                                listener.onUpdatingCompletion(url);
                            }
                        }
                    });
                }
            });
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            OnUpdateResourceListener listener = mArgument.mListenerRef.get();
            if (listener != null) {
                listener.onUpdateStart();
            }
        }

        @Override
        protected Void doInBackground(Void... params) {
            ResourceItem[] resItems = mArgument.mResourceItems;
            final int n = resItems != null ? resItems.length : 0;
            for (int i = 0; i < n; i++) {
                final ResourceItem item = resItems[i];
                mDownloader.load(item.url, item.name);
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            OnUpdateResourceListener listener = mArgument.mListenerRef.get();
            if (listener != null) {
                listener.onUpdateFinish();
            }
        }
    }

    private static class DownloaderAdapter {

        void onStart() {}

        void onCompletion(String url) {}

        void onFinish(boolean completion) {}
    }

    private static class Downloader {

        private String mRoot = null;
        private OkHttpClient mClient = null;
        private DownloaderAdapter mCallback = null;

        Downloader(String root) {
            mRoot = root;
            mClient = new OkHttpClient();
        }

        void setCallback(DownloaderAdapter callback) {
            mCallback = callback;
        }

        void load(String url, String name) {
            Request.Builder builder = new Request.Builder()
                    .url(url);
            boolean suc = false;
            try {
                if (mCallback != null) {
                    mCallback.onStart();
                }

                String filePath = mRoot + File.separator + name;
                File f = new File(filePath);
                if (f.exists()) {
                    f.delete();
                }
                if (f.createNewFile()) {
                    Response response = mClient.newCall(builder.build()).execute();
                    if (response.isSuccessful()) {
                        InputStream in = response.body().byteStream();
                        OutputStream out = new FileOutputStream(f);
                        int bufferedLength = 4 * 1024 * 1024;
                        byte[] buf = new byte[bufferedLength];
                        int read;
                        while ((read = in.read(buf)) != -1) {
                            out.write(buf, 0, read);
                            out.flush();
                        }
                        out.close();
                        in.close();
                        if (mCallback != null) {
                            mCallback.onCompletion(url);
                            suc = true;
                        }
                    }
                }

            } catch (IOException e) {
                e.printStackTrace();
            }

            if (mCallback != null) {
                mCallback.onFinish(suc);
            }

        }
    }
}
